using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Microblaze.Simulator.Engine;

namespace Weazel.Microblaze.Simulator.Components.Microblaze.Instructions
{
    /// <summary>
    /// Unconditional Branch
    /// </summary>
    public class Br : Instruction
    {
        private enum BrInstructionKind
        {
            None,
            br,
            bra,
            brd,
            brad,
            brld,
            brald
        }

        private BrInstructionKind kind;

        public override int CycleCount
        {
            get
            {
                return
                  kind == BrInstructionKind.brad ||
                  kind == BrInstructionKind.brald ||
                  kind == BrInstructionKind.brd ||
                  kind == BrInstructionKind.brld ? 2 : 3;
            }
        }

        public Br()
            : base("br")
        {
        }

        public override void Set(uint address, uint value)
        {
            base.Set(address, value);

            kind =
              (SourceRegisterA == 0x00) ? BrInstructionKind.br :
              (SourceRegisterA == 0x08) ? BrInstructionKind.bra :
              (SourceRegisterA == 0x10) ? BrInstructionKind.brd :
              (SourceRegisterA == 0x18) ? BrInstructionKind.brad :
              (SourceRegisterA == 0x14) ? BrInstructionKind.brld :
              (SourceRegisterA == 0x1C) ? BrInstructionKind.brald :
              BrInstructionKind.None;

            if (kind == BrInstructionKind.None)
                throw new UnknownInstructionException(value);
        }

        /// <summary>
        /// Branch to the instruction located at address determined by rB. The 
        /// mnemonics brld and brald will set the L bit. If the L bit is set, 
        /// linking will be performed. The current value of PC will be stored 
        /// in rD. The mnemonics bra, brad and brald will set the A bit. If the 
        /// A bit is set, it means that the branch is to an absolute value and 
        /// the target is the value in rB, otherwise, it is a relative branch 
        /// and the target will be PC + rB. The mnemonics brd, brad, brld and 
        /// brald will set the D bit. The D bit determines whether there is a 
        /// branch delay slot or not. If the D bit is set, it means that there 
        /// is a delay slot and the instruction following the branch (i.e. in 
        /// the branch delay slot) is allowed to complete execution before 
        /// executing the target instruction. If the D bit is not set, it means 
        /// that there is no delay slot, so the instruction to be executed 
        /// after the branch is the target instruction.
        /// </summary>    
        public override void Execute(MicroblazeComponent MicroblazeComponent)
        {
            bool L =
              kind == BrInstructionKind.brld ||
              kind == BrInstructionKind.brald;

            bool A =
              kind == BrInstructionKind.bra ||
              kind == BrInstructionKind.brad ||
              kind == BrInstructionKind.brald;

            bool D =
              kind == BrInstructionKind.brad ||
              kind == BrInstructionKind.brald ||
              kind == BrInstructionKind.brd ||
              kind == BrInstructionKind.brld;

            if (L)
            {
                // store the current PC in register
                MicroblazeComponent.WriteRegister(
                  DestinationRegister,
                  MicroblazeComponent.Pc,
                  Address
                );
            }

            if (A)
            {
                MicroblazeComponent.Pc = MicroblazeComponent.ReadRegister(SourceRegisterB);
            }
            else
            {
                MicroblazeComponent.Pc = MicroblazeComponent.Pc + MicroblazeComponent.ReadRegister(SourceRegisterB);
            }

            if (D)
            {
                MicroblazeComponent.DelayedBranchActive = true;
            }
        }

        public override string ToString()
        {
            if (kind == BrInstructionKind.brld || kind == BrInstructionKind.brald)
                return string.Format("{0} r{1}, r{2}",
                  kind,
                  DestinationRegister,
                  SourceRegisterB
                );
            else
                return string.Format("{0} r{1}",
                  kind,
                  SourceRegisterB
                );
        }
    }
}
